﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml;

using EnvDTE;

using Microsoft.Practices.RecipeFramework.Library;
using System.ComponentModel.Design;

namespace ProjectBase.Guidance.Helpers
{
    public static class SolutionHelper
    {
        public static Project FindProject(DTE dte, ProjectType projectType)
        {
            return FindProject(dte, projectType.ToString().Replace("_", "."));
        }

        public static Project FindProject(DTE dte, string projectType)
        {
            Predicate<Project> projectTypePredicate = CreateProjectTypePredicate(dte, projectType);

            Project currentProject = DteHelper.GetSelectedProject(dte);
            if (currentProject != null)
            {
                if (projectTypePredicate(currentProject))
                {
                    return currentProject;
                }
                string suggestedProjectName = projectType.Contains(".")
                    ? projectType
                    : currentProject.Name.Substring(0, currentProject.Name.LastIndexOf('.') + 1) + projectType;
                Project suggestedProject = FindProjectByName(dte, suggestedProjectName);
                if (suggestedProject != null && projectTypePredicate(suggestedProject))
                {
                    return suggestedProject;
                }
            }

            return FindProject(dte, projectTypePredicate);
        }

        private static Predicate<Project> CreateProjectTypePredicate(DTE dte, string projectType)
        {
            return FindProjectType(projectType);
        }

        public static ProjectItem FindItemByExtension(ProjectItems pis, string extension)
        {
            ProjectItem result = null;
            foreach (ProjectItem pi in pis)
            {
                if (pi.Kind == Constants.vsProjectItemKindPhysicalFile)
                {
                    if (Path.GetExtension(pi.Name).Equals(extension, StringComparison.InvariantCultureIgnoreCase))
                    {
                        result = pi;
                        break;
                    }
                }
                else if (pi.Kind == Constants.vsProjectItemKindPhysicalFolder)
                {
                    result = FindItemByExtension(pi.ProjectItems, extension);
                    if (result != null)
                    {
                        break;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// A FindItemByPath like the one in DteHelper, but that works with projects that are
        /// in Solution Folders.
        /// </summary>
        /// <param name="solution">The solution to look in.</param>
        /// <param name="itemPath">The relative path of the item.</param>
        /// <returns>The ProjectItem, or null if not found.</returns>
        public static ProjectItem FindItemBySolutionPath(Solution solution, string itemPath)
        {
            ProjectItem result = null;

            string[] parts = itemPath.Split(new char[] { '\\', '/' });
            if (parts.Length >= 2)
            {
                // The first part is the project name.
                Project project = FindProjectByName(solution.DTE, parts[0]);
                if (project != null)
                {
                    // All subsequent parts are the path to the project item within the project.
                    ProjectItems items = project.ProjectItems;
                    for (int index = 1; index < parts.Length; index++)
                    {
                        if (items == null)
                        {
                            result = null;
                        }
                        else
                        {
                            result = DteHelper.FindItemByName(items, parts[index], false);
                        }
                        if (result == null)
                        {
                            break;
                        }
                        items = result.ProjectItems;
                    }
                }
            }

            return result;
        }

        public static void ReloadSolution(DTE dte)
        {
            string solFile = dte.Solution.Properties.Item("Path").Value.ToString();
            dte.Solution.Close(true);
            dte.Solution.Open(solFile);
        }

        public static void FixProjectReferences(DTE dte)
        {
            Hashtable projectGuids = new Hashtable();
            string solFile = dte.Solution.Properties.Item("Path").Value.ToString();
            string solDir = Path.GetDirectoryName(solFile);
            string projGuid;
            string asmName;

            // Dummy build, forcing the project guids to be inserted by Studio
            dte.Solution.SolutionBuild.Build(true);

            XmlDocument proj = new XmlDocument();
            XmlNamespaceManager nsm = new XmlNamespaceManager(proj.NameTable);
            //nsm.AddNamespace("ns", Config.GetValue(dte.Solution, "ProjectNamespace"));

            // First pass, gather all project GUIDs
            foreach (string dirPath in Directory.GetDirectories(solDir))
            {
                foreach (string projPath in Directory.GetFiles(dirPath, "*.csproj"))
                {
                    proj.Load(projPath);

                    projGuid = proj.SelectSingleNode("//ns:ProjectGuid", nsm).InnerText;

                    asmName = proj.SelectSingleNode("//ns:AssemblyName", nsm).InnerText;

                    if (projGuid.Length > 0)
                    {
                        projectGuids[asmName] = projGuid;
                    }
                }
            }

            // Second pass, set referenced project GUIDs
            foreach (string dirPath in Directory.GetDirectories(solDir))
            {
                foreach (string projPath in Directory.GetFiles(dirPath, "*.csproj"))
                {
                    proj.Load(projPath);
                    foreach (XmlNode node in proj.SelectNodes("//ns:ProjectReference", nsm))
                    {
                        asmName = node.SelectSingleNode("ns:Name", nsm).InnerText;
                        if (asmName.Length > 0)
                        {
                            projGuid = Utils.SafeToString(projectGuids[asmName]);
                            if (projGuid.Length > 0)
                            {
                                node.SelectSingleNode("ns:Project", nsm).InnerText = projGuid;
                            }
                        }
                    }

                    SourceControlHelper.Checkout(dte, projPath);

                    proj.Save(projPath);
                }
            }

            SourceControlHelper.Checkout(dte, solFile);

            dte.Solution.SaveAs(solFile);
        }

        /// <summary>
        /// in 'project' searches for folder with name same as namespace (without project defaultNamespace)
        /// If folder is not found, new is created
        /// </summary>
        /// <param name="project"></param>
        /// <param name="Namespace">namspace string with possible defaultNamespace of project (will be removed)</param>
        /// <returns></returns>
        public static ProjectItem FindOrCreateSelectedFolderInGivenProject(Project project, string Namespace)
        {
            string path = string.Empty;
            // find the namespace part after project default namespace
            string ns = Namespace.Replace(project.Properties.Item("DefaultNamespace").Value + ".", string.Empty);
            foreach (string str in ns.Split('.'))
            {
                // create folders dependent on namespaces
                path = Path.Combine(path, str);
            }

            ProjectItem projectItem =
                FindItemBySolutionPath(project.DTE.Solution, path); //Path.Combine(project.Name, path));

            if (projectItem == null && path.Length > 0)
            {
                // path not found in the given project, create it just-in-time
                projectItem = AddFolderPath(project.ProjectItems, path);
            }
            return projectItem;
        }

        public static ProjectItem FindOrCreateSelectedFolderInGivenProject(Project project)
        {
            string path = VisualStudioHelper.CurrentTargetPath(project.DTE);
            path = Path.HasExtension(path) ? Path.GetDirectoryName(path) : path;

            ProjectItem projectItem =
                FindItemBySolutionPath(project.DTE.Solution, Path.Combine(project.Name, path));

            if (projectItem == null && path.Length > 0)
            {
                // path not found in the given project, create it just-in-time
                projectItem = AddFolderPath(project.ProjectItems, path);
            }
            return projectItem;
        }

        public static Project FindProjectByName(DTE dte, string projectName)
        {
            return FindProject(dte, CompareProjectNamePredicate(projectName));
        }

        public static string FindProjectUIPath(DTE dte, Project project)
        {
            return FindProjectUIPath(dte.Solution, CompareProjectPredicate(project));
        }

        private static string FindProjectUIPath(Solution solution, Predicate<Project> predicate)
        {
            // Loop through all top-level projects to find a project.
            string foundPath = null;
            foreach (Project project in solution.Projects)
            {
                foundPath =
                    RecursiveFindProjectUIPath((string)solution.Properties.Item("Name").Value, project, predicate);
                if (foundPath != null)
                {
                    break;
                }
            }
            return foundPath;
        }

        private static string RecursiveFindProjectUIPath(string path, Project project,
                                                         Predicate<Project> predicate)
        {
            string foundPath = null;

            if (project.ProjectItems != null)
            {
                foreach (ProjectItem projectItem in project.ProjectItems)
                {
                    if (projectItem.SubProject != null)
                    {
                        // If the project contains an item that actually is a SubProject, we need
                        // to handle this recursively.  This will make sure we find projects
                        // that are in Solutions Folders.
                        foundPath =
                            RecursiveFindProjectUIPath(Path.Combine(path, project.Name), projectItem.SubProject,
                                                       predicate);
                        if (foundPath != null)
                        {
                            break;
                        }
                    }
                }
            }

            if (foundPath == null && predicate(project))
            {
                foundPath = Path.Combine(path, project.Name);
            }

            return foundPath;
        }

        public static Project FindProject(DTE dte, Predicate<Project> predicate)
        {
            return FindProject(dte.Solution.Projects, predicate);
        }

        public static string GetTargetPathOfProjectItem(ProjectItem target)
        {
            string targetPath = target.Properties.Item("LocalPath").Value.ToString();
            string projectPath = target.ContainingProject.Properties.Item("LocalPath").Value.ToString();
            string relativeTargetPath = targetPath.Replace(projectPath, "");
            if (relativeTargetPath.EndsWith("\\"))
            {
                relativeTargetPath = relativeTargetPath.Substring(0, relativeTargetPath.Length - 1);
            }
            return relativeTargetPath;
        }

        private static Project FindProject(Projects projects, Predicate<Project> predicate)
        {
            // Loop through all top-level projects to find a project.
            Project foundProject = null;
            foreach (Project project in projects)
            {
                foundProject = RecursiveFindProject(project, predicate);
                if (foundProject != null)
                {
                    break;
                }
            }
            return foundProject;
        }

        private static Project RecursiveFindProject(Project project, Predicate<Project> predicate)
        {
            Project foundProject = null;

            if (project.ProjectItems != null)
            {
                foreach (ProjectItem projectItem in project.ProjectItems)
                {
                    if (projectItem.SubProject != null)
                    {
                        // If the project contains an item that actually is a SubProject, we need
                        // to handle this recursively.  This will make sure we find projects
                        // that are in Solutions Folders.
                        foundProject = RecursiveFindProject(projectItem.SubProject, predicate);
                        if (foundProject != null)
                        {
                            break;
                        }
                    }
                }
            }

            if (foundProject == null && predicate(project))
            {
                foundProject = project;
            }

            return foundProject;
        }

        private static Predicate<Project> CompareProjectNamePredicate(string projectName)
        {
            return
                delegate(Project project) { return project.Name.Equals(projectName, StringComparison.InvariantCultureIgnoreCase); };
        }

        private static Predicate<Project> CompareProjectPredicate(Project projectToFind)
        {
            return delegate(Project project) { return project == projectToFind; };
        }

        private static Predicate<Project> FindProjectType(string projectType)
        {
            return
                delegate(Project project) { return ProjectPredicate(project.Name, projectType); };
        }

        public static bool ProjectPredicate(string projectName, string projectType)
        {
            if (projectType.Contains("ProjectBase"))
            {
                return projectName.Contains(projectType.Replace("_", "."));
            }
            return !projectName.Contains("ProjectBase")
                && projectName.EndsWith("." + projectType);
        }

        public static ProjectItem FindSolutionItemByName(DTE dte, string name, bool recursive)
        {
            ProjectItem projectItem = null;
            foreach (Project project in dte.Solution.Projects)
            {
                projectItem = FindProjectItemInProject(project, name, recursive);

                if (projectItem != null)
                {
                    break;
                }
            }
            return projectItem;
        }

        public static ProjectItem AddFolderPath(ProjectItems projectItems, string path)
        {
            ProjectItem projectItem = null;

            string[] folders = path.Split(new char[] { '\\', '/' });
            string folderPath = projectItems.ContainingProject.Name;

            foreach (string folder in folders)
            {
                folderPath = Path.Combine(folderPath, folder);
                projectItem = FindItemBySolutionPath(projectItems.DTE.Solution, folderPath);
                if (projectItem == null)
                {
                    SourceControlHelper.Checkout(projectItems.ContainingProject);
                    projectItem = projectItems.AddFolder(folder, Constants.vsProjectItemKindPhysicalFolder);
                }
                projectItems = projectItem.ProjectItems;
            }
            return projectItem;
        }

        public static ProjectItems GetProjectItems(object target)
        {
            Project project = target as Project;

            if (project != null)
            {
                return project.ProjectItems;
            }
            else
            {
                ProjectItem projectItem = target as ProjectItem;

                if (projectItem != null)
                {
                    return projectItem.ProjectItems;
                }
            }

            return null;
        }

        public static bool IsProjectOfProjectType(Project project, ProjectType projectType)
        {
            return ProjectPredicate(project.Name, projectType.ToString());
        }

        public static string GetProjectName(DTE dte, string projectType)
        {
            Project p = FindProject(dte, projectType);
            return p == null ? String.Empty : p.Name;
        }

        public static string GetProjectName(DTE dte, ProjectType projectType)
        {
            Project p = FindProject(dte, projectType);

            return p == null ? String.Empty : p.Name;
        }

        public static string GetProjectPath(Project p)
        {
            string path = String.Empty;
            if (p != null && p.FileName.Length > 0)
            {
                path = Path.GetDirectoryName(p.FileName);
            }
            return path;
        }

        public static string GetSolutionPath(Solution s)
        {
            string path = String.Empty;
            if (s != null && s.Properties.Item("Path").Value.ToString().Length > 0)
            {
                path = Path.GetDirectoryName(s.Properties.Item("Path").Value.ToString());
            }
            return path;
        }

        public static string GetSolutionPrefix(string solutionName)
        {
            if (solutionName.Contains("."))
            {
                return solutionName.Substring(0, solutionName.LastIndexOf('.'));
            }
            else
            {
                return string.Empty;
            }
        }

        public static string GetSolutionPrefix(DTE dte, IDictionaryService dictionaryService)
        {
            string solutionName = Utils.SafeToString(dictionaryService.GetValue("SolutionName"));

            if (solutionName.Length == 0)
            {
                solutionName = Utils.SafeToString(dte.Solution.Properties.Item("Name").Value);
            }

            return SolutionHelper.GetSolutionPrefix(solutionName);
        }

        public static bool TargetContainsFacade(object target)
        {
            return TargetContainsFileThatEndsWithSpecifiedString(target, "Facade.cs");
        }

        public static bool TargetContainsScenarioMap(object target)
        {
            return TargetContainsFileThatEndsWithSpecifiedString(target, "ScenarioMap.xml");
        }

        public static bool TargetContainsFileThatEndsWithSpecifiedString(object target, string nameToSearchFor)
        {
            ProjectItem projectItem = target as ProjectItem;

            if (projectItem != null)
            {
                if (projectItem.ProjectItems != null)
                {
                    foreach (ProjectItem childItem in projectItem.ProjectItems)
                    {
                        if (childItem.Name.EndsWith(nameToSearchFor))
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public static bool IsTargetSpecifiedFolder(object target, ProjectType projectType)
        {
            ProjectItem projectItem = target as ProjectItem;

            if (projectItem != null)
            {
                return IsProjectOfProjectType(projectItem.ContainingProject, projectType);
            }
            else
            {
                return IsTargetSpecifiedProject(target, projectType);
            }
        }

        public static bool IsTargetSpecifiedProject(object target, ProjectType projectType)
        {
            Project project = target as Project;

            if (project != null)
            {
                return IsProjectOfProjectType(project, projectType);
            }

            return false;
        }

        public static bool IsEntityProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.Entity);
        }

        public static bool IsDataProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.Data);
        }

        public static bool IsBusinessProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.Business);
        }

        public static bool IsRestApiProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.RestApi);
        }

        public static bool IsModelProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.Mvc);
        }

        public static bool IsControllerProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.Mvc);
        }

        public static bool IsTestsProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.Tests);
        }

        public static bool IsWindowsProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.UI);
        }

        public static bool IsWebProject(object target)
        {
            return IsTargetSpecifiedProject(target, ProjectType.Web);
        }

        public static Project GetCurrentProject(DTE dte)
        {
            object target = DteHelper.GetTarget(dte);

            ProjectItem projectItem = target as ProjectItem;

            if (projectItem != null)
            {
                return projectItem.ContainingProject;
            }
            else
            {
                Project project = target as Project;

                if (project != null)
                {
                    return project;
                }
            }

            return null;
        }

        public static string GetCurrentProjectPath(DTE dte)
        {
            Project project = GetCurrentProject(dte);
            return GetProjectPath(project);
        }

        public static ProjectItem FindProjectItemInProject(Project project, string name, bool recursive)
        {
            ProjectItem projectItem = null;

            if (project.Kind != Constants.vsProjectKindSolutionItems)
            {
                if (project.ProjectItems != null && project.ProjectItems.Count > 0)
                {
                    projectItem = DteHelper.FindItemByName(project.ProjectItems, name, recursive);
                }
            }
            else
            {
                // if the project is a solution folder, one of its ProjectItems might be a real project
                foreach (ProjectItem item in project.ProjectItems)
                {
                    Project realProject = item.Object as Project;

                    if (realProject != null)
                    {
                        projectItem = FindProjectItemInProject(realProject, name, recursive);

                        if (projectItem != null)
                        {
                            break;
                        }
                    }
                }
            }

            return projectItem;
        }

        public static List<ProjectItem> FindResourceFilesInUIProject(DTE vs)
        {
            List<ProjectItem> resxFiles = new List<ProjectItem>();
            Project webProject = FindProject(vs, ProjectType.Web);

            AddFilesWithExtensionToList(webProject.ProjectItems, resxFiles, ".resx");

            return resxFiles;
        }

        private static void AddFilesWithExtensionToList(ProjectItems projectItems, List<ProjectItem> resultFiles, string extension)
        {
            foreach (ProjectItem projectItem in projectItems)
            {
                if (projectItem.ProjectItems.Count > 0)
                {
                    AddFilesWithExtensionToList(projectItem.ProjectItems, resultFiles, extension);
                }

                if (projectItem.Name.EndsWith(extension, StringComparison.InvariantCultureIgnoreCase))
                {
                    resultFiles.Add(projectItem);
                }
            }
        }

        public static List<ProjectItem> FindAspxPagesInUIProject(DTE vs)
        {
            List<ProjectItem> aspxPages = new List<ProjectItem>();
            Project webProject = FindProject(vs, ProjectType.Web);

            AddAspxPagesToList(webProject.ProjectItems, aspxPages);

            return aspxPages;
        }

        private static void AddAspxPagesToList(ProjectItems projectItems, List<ProjectItem> pages)
        {
            foreach (ProjectItem projectItem in projectItems)
            {
                if (projectItem.ProjectItems.Count > 0)
                {
                    AddAspxPagesToList(projectItem.ProjectItems, pages);
                }

                if (projectItem.Name.EndsWith(".aspx"))
                {
                    pages.Add(projectItem);
                }
            }
        }

        public static void CreateOrOverwriteFileInProjectItemWithContentFromOtherFile(
            ProjectItem parentItem, string fileName, string otherFileName)
        {
            ProjectItem item = DteHelper.FindItemByName(parentItem.ProjectItems, fileName, false);

            if (item == null)
            {
                parentItem.ProjectItems.AddFromTemplate(otherFileName, fileName);
            }
            else
            {
                SourceControlHelper.Checkout(item);

                using (StreamWriter writer = new StreamWriter(item.Properties.Item("LocalPath").Value.ToString()))
                {
                    writer.WriteLine(File.ReadAllText(otherFileName));
                }
            }
        }

        public static void CreateOrOverwriteFileInProjectRootWithContentFromOtherFile(
            Project parentProject, string fileName, string otherFileName)
        {
            ProjectItem item = DteHelper.FindItemByName(parentProject.ProjectItems, fileName, false);

            if (item == null)
            {
                parentProject.ProjectItems.AddFromTemplate(otherFileName, fileName);
            }
            else
            {
                SourceControlHelper.Checkout(item);

                using (StreamWriter writer = new StreamWriter(item.Properties.Item("LocalPath").Value.ToString()))
                {
                    writer.WriteLine(File.ReadAllText(otherFileName));
                }
            }
        }

        public static bool IsTargetSolution(object target)
        {
            return (target is Solution);
        }

        public static bool TargetParentContainsFacade(object target)
        {
            return TargetContainsFileThatEndsWithSpecifiedString((target as ProjectItem).Collection.Parent, "Facade.cs");
        }

        public static ProjectItem GetParentProjectItem(ProjectItem projectItem)
        {
            if (projectItem == null) { return null; }

            // if the project is a solution folder, one of its ProjectItems might be a real project
            foreach (ProjectItem item in projectItem.ContainingProject.ProjectItems)
            {
                ProjectItem parent = GetParentProjectItem(item, projectItem);
                if (parent != null)
                {
                    return parent;
                }
            }
            return null;
        }

        private static ProjectItem GetParentProjectItem(ProjectItem parentOrGrandParentprojectItem, ProjectItem projectItem)
        {
            if (projectItem == null) { return null; }

            // if the project is a solution folder, one of its ProjectItems might be a real project
            foreach (ProjectItem item in parentOrGrandParentprojectItem.ProjectItems)
            {
                if (item == projectItem)
                {
                    return parentOrGrandParentprojectItem;
                }
                else
                {
                    ProjectItem parent = GetParentProjectItem(item, projectItem);
                    if (parent != null)
                    {
                        return parent;
                    }
                }
            }
            return null;
        }
    }
}